Rust学习资料
最近在研究Rust,目前大多数项目都可以使用Rust开发,但是涉及到和其他语言交互,比如用Rust开发一个SDK,一般还是需要导出C接口。
那如何将Rust导出C接口?
Rust的FFI就是专门做这件事的。一个正常的Rust public接口长这样:
pub fn hello_world() -> i32 {
20
}
如果要把一个Rust函数导出为C接口,需要对它进行改造:
#[no_mangle]
pub extern "C" fn hello_world() -> i32 {
20
}
它相比于纯Rust函数有两点不同:
一个是extern "C":表示导出C接口
一个是#[no_mangle]:正常一个C++或者Rust函数相关的符号都特别长且难以理解,使用它表示导出的符号是C符号,即没有任何多余的修饰,函数名是啥样,相关的符号大概就是啥样,如图:
如何导出C的动态库或者静态库?
如果想要编译出动态库或者静态库,可以在Cargo.toml中配置crate-type:
[lib]
crate-type = ["cdylib"] # Creates dynamic lib
# crate-type = ["staticlib"] # Creates static lib
cylib表示导出动态库,staticlib表示导出静态库。
如何生成对应的C头文件?
一个C库一般都有个头文件,那Rust怎么生成一个C的头文件呢?可以使用cbindgen:
cbindgen --config cbindgen.toml --crate hello_world --output hello_world.h
其中cbindgen.toml是一个template文件,我后面链接中列了具体地址。
上面的hello_world函数,我使用cbindgen就可以自动生成一个头文件:
#pragma once
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
int32_t hello_world(void);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
至于如何使用更复杂的类型和C交互,比如我想导出和传入一个结构体的指针,比如我想设置const char*,以及怎么管理对应的内存?
可以直接看这段代码:
use std::boxed::Box;
use std::ffi::CStr;
use std::ffi::CString;
use std::os::raw::c_char;
pub struct Manager {
path: CString,
}
fn get_default_cstring() -> CString {
CString::new("").expect("new string failed")
}
#[no_mangle]
pub extern "C" fn manager_create() -> *mut Manager {
println!("{}", "create_manager().".to_string());
Box::into_raw(Box::new(Manager {
path: get_default_cstring(),
}))
}
#[no_mangle]
pub extern "C" fn manager_destroy(ptr: *mut Manager) {
if ptr.is_null() {
return;
}
// safe
unsafe {
let _b = Box::from_raw(ptr);
}
}
impl Manager {
#[no_mangle]
pub extern "C" fn manager_set_path(&mut self, p: *const c_char) {
unsafe {
self.path = CString::from(CStr::from_ptr(p));
}
}
#[no_mangle]
pub extern "C" fn manager_get_function(&self) -> *const c_char {
self.path.as_ptr()
}
}
#[no_mangle]
pub extern "C" fn hello_world() -> i32 {
20
}
也许有人不太理解Rust代码的含义,那可以直接看它对应的C Header:
#pragma once
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
typedef struct Manager Manager;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
int32_t hello_world(void);
struct Manager *manager_create(void);
void manager_destroy(struct Manager *ptr);
const char *manager_get_function(const struct Manager *self);
void manager_set_path(struct Manager *self, const char *p);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
通过manager_create创建的内存,需要通过manager_destroy销毁。
但是在对应的Rust代码没有用到申请或者销毁内存相关的代码,而是使用Box来管理内存,它可以理解为C++中的unique_ptr。
内存都通过对象来管理,避免Rust申请的内存,让C这边来释放,违反代码的开发准则。
当然,如果你非要在Rust层想malloc和free内存,可以使用libc的crate。
如果想在Rust中调用C或者C++代码,可以使用cxx crate,也很方便,比如Rust中的String、Vec都在cxx中有对应的类型。
但是我的目的是使用Rust开发一个C的动态库的SDK,两种方法都尝试了下,感觉还是直接使用FFI更方便些。
我这里特意整理了一些Rust FFI相关资料,感兴趣的可以看看
Rust如何调用C接口、Rust如何导出C接口、C的callback如何传给Rust:https://doc.rust-lang.org/nomicon/ffi.html
The Embedded Rust Book FFI:https://docs.rust-embedded.org/book/interoperability/rust-with-c.html
使用cbindgen可以自动分析,将Rust接口导出为C接口:https://github.com/eqrion/cbindgen
cbindgen的default template:https://github.com/eqrion/cbindgen/blob/master/template.toml
cbindgen的doc:https://github.com/eqrion/cbindgen/blob/master/docs.md
Rust FFI的 example blog,主要是string如何传出去并销毁相关内存:https://snacky.blog/en/string-ffi-rust.html
cxx专用于Rust和C++之间的桥梁:https://github.com/dtolnay/cxx
cxx:https://cxx.rs/
Complex data types and Rust FFI blog:http://kmdouglass.github.io/posts/complex-data-types-and-the-rust-ffi/
Rust std ffi:https://doc.rust-lang.org/std/ffi/index.html
与C交互时,可以使用libc在Rust层做C的malloc和free
涉及ptr的地方需要了解:https://doc.rust-lang.org/std/ptr/index.html
还有些Rust入门资料:
https://www.zhihu.com/question/31038569
https://doc.rust-lang.org/rust-by-example/
https://doc.rust-lang.org/cargo/getting-started/installation.html
https://github.com/rustlang-cn/Rustt
https://github.com/sunface/rust-course
更多内容在 一个优质的C++学习圈 里,来一起钻研C++和Rust吧。